package utils
{
	public class Game_actorpool
	{
		// list of string names of each known kind
		private var all_names:Vector.<String>;
		// contains one source actor for each kind
		private var all_kinds:Dictionary;
		// contains many cloned actors of various kinds
		private var all_actors:Dictionary;
		// temporary variables - used often
		private var actor:Game_actor;
		private var actor_list:Vector.<Game_actor>;
		// used only for stats
		public var actors_created:uint = 0;
		public var actors_active:uint = 0;
		public var totalpolycount:uint = 0;
		public var totalrendered:uint = 0;
		// so we can "pause" any stepping for actors
		// as a group for efficiency
		public var active:Boolean = true;
		// so we can hide parts of the map if required
		// perfect for rooms/portals/pvs/lod
		public var visible:Boolean = true;
		
		public function Game_actorpool()
		{
			trace("Actor pool created.");
			all_kinds = new Dictionary();
			all_actors = new Dictionary();
			all_names = new Vector.<String>;
		}
		
		// names a particular kind of actor
		public function define_actor(name:String, clone_source:Game_actor):void
		{
			trace("New actor type defined: " + name);
			all_kinds[name] = clone_source;
			all_names.push(name);
		}
		
		public function step(ms:uint,
							 collision_detection:Function = null,
							 collision_reaction:Function = null):void
		{
			// do nothing if entire pool is inactive (paused)
			if (!active) return;
			actors_active = 0;
			for each (actor_list in all_actors)
			{
				for each (actor in actor_list)
				{
					if (actor.active)
					{
						actors_active++;
						actor.step(ms);
						if (actor.collides &&
							(collision_detection != null))
						{
							actor.touching = collision_detection(actor);
							if (actor.touching &&
								(collision_reaction != null))
								collision_reaction(actor, actor.touching);
						}
					}
				}
			}
		}
		
		// renders all active actors
		public function render(view:Matrix3D,projection:Matrix3D):void
		{
			// do nothing if entire pool is invisible
			if (!visible) return;
			totalpolycount = 0;
			totalrendered = 0;
			for each (actor_list in all_actors)
			{
				for each (actor in actor_list)
				{
					if (actor.active && actor.visible)
					{
						totalpolycount += actor.polycount;
						totalrendered++;
						actor.render(view, projection);
					}
				}
			}
		}
		
		// either reuse an inactive actor or create a new one
		// returns the actor that was spawned for further use
		public function spawn(name:String, pos:Matrix3D = null):Game_actor
		{
			var spawned:Game_actor = null;
			var reused:Boolean = false;
			if (all_kinds[name])
			{
				if (all_actors[name])
				{
					for each (actor in all_actors[name])
					{
						if (!actor.active)
						{
							//trace("A " + name + " was reused.");
							actor.respawn(pos);
							spawned = actor;
							reused = true;
							return spawned;
						}
					}
				}
				else
				{
					//trace("This is the first " + name + " actor.");
					all_actors[name] = new Vector.<Game_actor>;
				}
				if (!reused) // no inactive ones were found
				{
					actors_created++;
					//trace("Creating a new " + name);
					//trace("Total actors: " + actors_created);
					spawned = all_kinds[name].cloneactor();
					spawned.classname = name;
					spawned.name = name + actors_created;
					spawned.respawn(pos);
					all_actors[name].push(spawned);
					//trace("Total " + name + "s: "
					//+ all_actors[name].length);
					return spawned;
				}
			}
			else
			{
				trace("ERROR: unknown actor type: " + name);
			}
			return spawned;
		}
		
		public function colliding(checkthis:Game_actor):Game_actor
		{
			if (!checkthis.visible) return null;
			if (!checkthis.active) return null;
			var hit:Game_actor;
			var str:String;
			for each (str in all_names)
			for each (hit in all_actors[str])
			{
				if (hit.visible &&
					hit.active &&
					checkthis.colliding(hit))
				{
					//trace(checkthis.name +
					//
					" is colliding with " + hit.name);
					return hit;
				}
				else
				{
					//trace(checkthis.name +
					//" is NOT colliding with " + hit.name);
				}
			}
			return null;
		}
		
		// optimize the scene: make actors that are far away
		// or outside a bounding box invisible
		public function hide_distant(pos:Vector3D,
									 // scaled by radius:
									 maxdist:Number = 500,
									 // radius is added to these:
									 maxz:Number = 0,
									 minz:Number = 0,
									 maxy:Number = 0,
									 miny:Number = 0,
									 maxx:Number = 0,
									 minx:Number = 0
		):void
		{
			for each (actor_list in all_actors)
			{
				for each (actor in actor_list)
				{
					if (actor.active)
					{
						if ((Vector3D.distance(actor.position, pos)
							>= (maxdist * actor.radius)) ||
							// if these are set to nonzero,
							// ensure we are inside a box at pos
							(maxx != 0 && ((pos.x + maxx) <
								(actor.position.x - actor.radius))) ||
							(maxy != 0 && ((pos.y + maxy) <
								(actor.position.y - actor.radius))) ||
							(maxz != 0 && ((pos.z + maxz) <
								(actor.position.z - actor.radius))) ||
							(minx != 0 && ((pos.x + minx) >
								(actor.position.x + actor.radius))) ||
							(miny != 0 && ((pos.y + miny) >
								(actor.position.y + actor.radius))) ||
							(minz != 0 && ((pos.z + minz) >
								(actor.position.z + actor.radius)))
						)
						{
							actor.visible = false;
						}
						else
						{
							actor.visible = true;
						}
					}
				}
			}
		}
		
		
	}
}